#include "driverlib.h"

/* start addresses for DSP text/data image and their length information
 * Their values are from flash image generation script of file 'gen_flash_image.py'
 */
#define DSP_IMAGE_START_ADDR      (0x30002400U)
#define DSP_IMAGE_INFO_START_ADDR (0x30002000U)
#define DMA_LENGTH                (256)

// bit[17:15] of _CORE<n>_IRAM_START
#define CPU_EXP_START_MASK (0x7)
#define CPU_EXP_START_OFST (15)

#define TRIM_VALID_KEY (0x5A5A5A5A)

static inline void copy_trim()
{
    uint32_t key = HWREG(FLASH0OTP1_BASE + 0);
    if (key != TRIM_VALID_KEY)
    {
        return;
    }

    HWREG(REGFILE_BASE + 0x24) = 0x55AA55AA; // unlock trim

    // copy trim data
    HWREG(REGFILE_BASE + 0) = HWREG(FLASH0OTP1_BASE + 0x4);
    HWREG(REGFILE_BASE + 4) = HWREG(FLASH0OTP1_BASE + 0x8);
    HWREG(REGFILE_BASE + 8) = HWREG(FLASH0OTP1_BASE + 0xC);

    HWREG(REGFILE_BASE + 0x20) = 1;          // enable efuse
    HWREG(REGFILE_BASE + 0x24) = 0xAA55AA55; // lock trim
}

static void dma_read(int src_addr, int dst_addr, int len)
{
    int tempSrc = src_addr;
    int tempDst = dst_addr;

    do
    {
        // 每次用DMA传输256字节
        DmaCh1Regs.SAR = tempSrc;
        DmaCh1Regs.DAR = tempDst;
        DmaRegs.CHEN   = 0x0101; // DMA CH1 启动

        while (!(DmaCh1Regs.INTSTATUS & 0x02)) // 等待结束中断标志位
            ;
        DmaCh1Regs.INTCLEAR = 0x02; // 清除结束中断标志位

        tempSrc = tempSrc + DMA_LENGTH;
        tempDst = tempDst + DMA_LENGTH;
        len     = len - DMA_LENGTH;

    } while (len > 0);
}

extern u32 _CORE0_CODE_ENTRY;
extern u32 _CORE0_IRAM_START;
extern u32 _CORE0_DRAM_START;
extern u32 _CORE1_IRAM_START;
extern u32 _CORE1_DRAM_START;
extern void _dsp_boot_jmp_to_core0(int);

//
// wait until GPIO_33 is logic 1
//
static void wait_gpio33_tobe1()
{
    // Enable GPIO_33 PULLUP, default is disabled
    // Read from GPIO_33 get 1 after pullup enable
    GpioCtrlRegs.GPBPUD.bit.GPIO33 = 0;
    while (0 == GpioDataRegs.GPBDAT.bit.GPIO33)
    { }

    // Set GPIO_33 PULLUP to default disable
    GpioCtrlRegs.GPBPUD.bit.GPIO33 = 1;
}

_Noreturn void qx_bootloader_main(void)
{
    //wait_gpio33_tobe1();

    // copy trim data from flash OTP area to regfile
    copy_trim();

    // set CPU interrupt vector table IRAM bank, [0,7]
    CpuSysRegs.CPU0EXPSTART.bit.EXPSTART
        = ((int)(&_CORE0_IRAM_START) >> CPU_EXP_START_OFST) & CPU_EXP_START_MASK;
    CpuSysRegs.CPU1EXPSTART.bit.EXPSTART
        = ((int)(&_CORE1_IRAM_START) >> CPU_EXP_START_OFST) & CPU_EXP_START_MASK;

    NmiIntruptRegs.NMICFG.bit.NMIE = 1; // 使能全局NMI中断

    // 注意:
    // 1. ECC DMEM 校验和检错功能必须在boot启动最初阶段就完成配置
    // 2. ECC IMEM 校验功能必须主程序里开启, boot中启动会导致启动卡死
    // 3. ECC DMEM 纠错功能开启后将只支持全域32位内存操作, 需要在软件保证没有对DMEM的8/16位操作
    // EccRegs.ECC_IMEM_MODE.bit.ECC_FLASH_EN = 1; // 使能FLASH ECC检错功能
    // EccRegs.ECC_DMEM_MODE.bit.CHECK_MODE = 1; // 1: DATA MEM检错, 2: DATA MEM纠错

    CpuSysRegs.PCLKCR0.bit.DMA = 1; // 使能DMA时钟
    // 配置DMA固定参数
    DmaRegs.CFG.all                = 0x03; // DMA 使能, 中断使能
    DmaCh1Regs.BLOCK_TS            = 0x3f; // DMA block为256字节
    DmaCh1Regs.CFG_H.all           = 0x00; // DMA MEM to MEM
    DmaCh1Regs.CTL_L.bit.SRC_WIDTH = 2;    // SRC 位宽为32Bits
    DmaCh1Regs.CTL_L.bit.DST_WIDTH = 2;    // DST 位宽为32 Bits
    DmaCh1Regs.INTSIGNAL_EN        = 0x02; // DMA中断只保留DMA传输结束中断

    int *p = (int *)(DSP_IMAGE_INFO_START_ADDR);

    int core0_text_len = *(p);
    int core0_data_len = *(p + 1);
    int core1_text_len = *(p + 2);
    int core1_data_len = *(p + 3);

    int flashCore0Text = DSP_IMAGE_START_ADDR;
    int flashCore0Data = DSP_IMAGE_START_ADDR + core0_text_len;
    int flashCore1Text = DSP_IMAGE_START_ADDR + core0_text_len + core0_data_len;
    int flashCore1Data = DSP_IMAGE_START_ADDR + core0_text_len + core0_data_len + core1_text_len;

    // 从FLASH搬运数据到SARM
    // 先搬运DATA_RAM以避免flash ecc出错时nmi_isr函数中无法访问数据段数据
    if (core0_data_len != 0)
        dma_read(flashCore0Data, (int)(&_CORE0_DRAM_START), core0_data_len);

    if (core0_text_len != 0)
        dma_read(flashCore0Text, (int)(&_CORE0_IRAM_START), core0_text_len);

    if (core1_data_len != 0)
        dma_read(flashCore1Data, (int)(&_CORE1_DRAM_START), core1_data_len);

    if (core1_text_len != 0)
        dma_read(flashCore1Text, (int)(&_CORE1_IRAM_START), core1_text_len);

    DmaRegs.RESET = 1;    // 复位DMA
    while (DmaRegs.RESET) // 等待复位
        ;
    CpuSysRegs.PCLKCR0.bit.DMA = 0; // 关闭DMA时钟

    // jump to DSP core 0 and execute its first instruction @_CORE0_CODE_ENTRY
    // @_CORE0_CODE_ENTRY is defined in link scripts and is configurable
    _dsp_boot_jmp_to_core0((int)(&_CORE0_CODE_ENTRY));

    // add infinite loop to make compiler happy on _Noreturn
    while (1)
        ;
}
